#!/usr/bin/env python3

import subprocess
import sys
import os
import time
import re
from optparse import OptionParser

log_file=None
prefix='export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/go/bin; cd /home/zstorage/zstorage/build;'
repo_pat = re.compile('git@gitee.com:(\w+)/(\w+).git')
g_root_dir = '/home/hy/match'
g_repo_status = 'scores.txt'
g_interval = 600

#
# 程序退到后台作为daemnon运行
#
def daemonize():
	try:
		pid = os.fork()
		if pid > 0:
			# exit first parent
			sys.exit(0)
	except OSError as err:
		sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
		sys.exit(1)
	# decouple from parent environment
	os.chdir('/')
	os.setsid()
	os.umask(0)
	# do second fork
	try:
		pid = os.fork()
		if pid > 0:
			# exit from second parent
			sys.exit(0)
	except OSError as err:
		sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
		sys.exit(1)
	# redirect standard file descriptors
	sys.stdout.flush()
	sys.stderr.flush()
	si = open(os.devnull, 'r')
	so = open(os.devnull, 'w')
	se = open(os.devnull, 'w')
	os.dup2(si.fileno(), sys.stdin.fileno())
	os.dup2(so.fileno(), sys.stdout.fileno())
	os.dup2(se.fileno(), sys.stderr.fileno())
#
# 输出信息，并记录日志
#
def errlog(logstr):
	global log_file
	print(logstr, end='')
	if log_file!=None:
		log_file.write(logstr)
		log_file.flush()
	return

#
# 运行某个命令
#
def exec_command(cmd):
	errlog('cmd=%s, begin\n' % cmd)
	p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
	ret = None
	ostr = []
	while True:
		outstr = p.stdout.readline().decode(encoding='utf-8')
		if ret!=None and outstr=='':
			break
		ostr.append(outstr)
		errlog(outstr)
		if ret==None:
			ret = p.poll()
	errlog('cmd=%s, ret=%d\n' % (cmd, ret))
	return ret, ostr

def git_clone(repo):
	ret, _ = exec_command(['git', 'clone', repo])
	return ret

def git_pull(repo):
	ret, _ = exec_command(['git', 'pull', repo])
	return ret

#git log -1 --oneline
def git_rev(repo):
	ret, ostr = exec_command(['git', 'log', '-1', '--oneline'])
	fds = ostr[0].split()
	return fds[0]

def get_repo_dir(repo):
	repo_dir = repo.replace('@', '_').replace('.', '_').replace(':','_').replace('/','_')
	return repo_dir

def pull_repo(repo):
	root_dir = os.getcwd()
	m = repo_pat.match(repo)
	account, repo_name = m.groups()
	repo_dir = get_repo_dir(repo)
	if os.path.exists(repo_dir):
		os.chdir(repo_dir)
		if os.path.exists(repo_name):
			os.chdir(repo_name)
			git_pull(repo)
		else:
			git_clone(repo)
			os.chdir(repo_name)
	else:
		os.mkdir(repo_dir)
		os.chdir(repo_dir)
		git_clone(repo)
		os.chdir(repo_name)
	rev = git_rev(repo)
	os.chdir(root_dir)
	cwd = os.getcwd()
	errlog('repo(%s) has been updated, rev = %s, cwd = %s\n' % 
		(repo, rev, cwd))
	return rev

def load_repo_status():
	repo_status = {}
	if not os.path.exists(g_repo_status):
		return repo_status
	lno = 0
	for i in open(g_repo_status):
		line = i[:-1].strip()
		lno += 1
		if line=='':
			continue
		if line[0]=='#':
			continue
		fds = line.split()
		if len(fds) < 3:
			errlog('load_repo_status(): error line(%d), skip\n' % 
				lno)
			continue
		repo, rev, score = fds[0], fds[1], float(fds[2])
		repo_status[repo] = (rev, score)
	return repo_status

def save_repo_status(repo_status):
	repo_list = []
	for repo in repo_status:
		rev, score = repo_status[repo]
		repo_list.append((score, repo, rev))
		print('%-40s %10s %10s\n' % (repo, rev, repr(score)))
	repo_list.sort(reverse=True)
	fp = open(g_repo_status, 'w+')
	fp.write("\n#\n# Notice: Do not modify this file which is generated by run.py automately. \n#\n\n")
	lno = 0
	for score, repo, rev in repo_list:
		lno += 1
		fp.write('%-40s %10s %10.2f\n' % (repo, rev, score))
	fp.close()

def compile_repo(repo):
	repo_dir = get_repo_dir(repo)
	m = repo_pat.match(repo)
	account, repo_name = m.groups()
	hash_src = '%s/%s/%s/hash.c' % (g_root_dir, repo_dir, repo_name)
	ret, ostr = exec_command(['gcc', '-g', '-O2', 'tester.c', hash_src, '-lpthread', '-lrt'])
	return ret

def run_repo(repo):
	ret, ostr = exec_command(['./a.out'])
	if ret!=0:
		return -1, 0.0
	lines = ostr
	pat = re.compile('total = (\d+)')
	score = 0.0
	for line in lines:
		m = pat.match(line)
		if not m:
			continue
		score = float(m.group(1))
	return 0, score

def git_upload_score():
	ret, ostr = exec_command(['git', 'add', 'scores.txt'])
	if ret != 0:
		errlog('git add error')
		return ret
	ret, ostr = exec_command(['git', 'commit', '-m', "'update scores.txt'"])
	if ret != 0:
		errlog('git commit error')
		return ret
	ret, ostr = exec_command(['git', 'push'])
	if ret != 0:
		errlog('git push error')
		return ret
	errlog('scores.txt is updated')
	return 0

def do_test():
	errlog("\n\nstart test at %s\n" %time.ctime())
	os.chdir(g_root_dir)
	repo_status = load_repo_status()
	for ll in open('players.txt'):
		line = ll[:-1].strip()
		if line=='':
			continue
		repo = line[:]
		errlog('try to update repo %s\n' % repo)
		rev = pull_repo(repo)
		if repo in repo_status and repo_status[repo][0]==rev:
			errlog('repo "%s" is updated(rev=%s), skip it\n' % 
				(repo, rev))
			continue
		r_rev = repo_status[repo][0] if repo in repo_status else '---'
		errlog('repo "%s" is not updated(rev=%s/%s), run it\n' % 
			(repo, rev, r_rev))
		#compile
		ret = compile_repo(repo)
		if ret != 0:
			score = 0.0
			repo_status[repo] = (rev, score)
			errlog('compile error, repo=%s\n' % repo)
			continue
		ret, score = run_repo(repo)
		if ret!=0:
			errlog('run error, repo=%s\n' % repo)
		repo_status[repo] = (rev, score)
	
	save_repo_status(repo_status)
	git_upload_score()

def main():
	global log_file, g_root_dir
	parser = OptionParser()
	parser.add_option("-r", "--root", dest="root_dir",
			help="the root directory", default="/home/hy/match")
	parser.add_option("-f", "--foreground",
			action="store_true", dest="foreground", default=False,
			help="running in the foreground")

	(options, args) = parser.parse_args()
	g_root_dir = options.root_dir
	log_file = open('%s/run.log' % g_root_dir, 'w+')
	errlog('g_root_dir = %s\n' % g_root_dir)

	if not options.foreground:
		daemonize()
	
	while True:
		do_test()
		time.sleep(g_interval)

if __name__=="__main__":
	main()

